package modeler;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.util.HashMap;

import javax.vecmath.AxisAngle4f;
import javax.vecmath.Matrix3f;
import javax.vecmath.Matrix4f;
import javax.vecmath.Vector3f;

import javax.media.opengl.GL;
import javax.media.opengl.glu.*;

/**
 * @author andru Transformation provides an interface to a 3D transformation
 *         matrix. It allows independent access to the translational,
 *         rotational, and scaling properties of the matrix. By convention,
 *         rotations are performed around the axes in X,Y,Z order.
 */

public class Transformation extends SceneNode {
  
  private static final long serialVersionUID = 3256437006337718072L;
  
  // Individual Translation, Rotation, and Scaling components of this transformation
  protected final Vector3f R = new Vector3f(); // Stored in degrees
  protected final Vector3f S = new Vector3f();
  protected final Vector3f T = new Vector3f();

  //Constants defining the coordinate axies
  public static final Vector3f xAxis = new Vector3f(1, 0, 0);
  public static final Vector3f yAxis = new Vector3f(0, 1, 0);
  public static final Vector3f zAxis = new Vector3f(0, 0, 1);

  /**
   * Required for IO
   */
  public Transformation() {}
  
  /**
   * Constructor
   * @param name
   */
  public Transformation(String name) {

    this.name = name;
    S.set(1, 1, 1);
  }

  /**
   * Copy constructor
   * @param copy
   */
  public Transformation(Transformation copy) {

    this.R.set(copy.R);
    this.S.set(copy.S);
    this.T.set(copy.T);
  }
  
  /**
   * Converts the given point v from local space to world space 
   */
  public void toWorld(Vector3f v, Vector3f outv) {
	  Vector3f result = new Vector3f();
	  transform(v, result);
	  if (parent == null) {
		  outv.set(result);
	  } else {
		  ((Transformation)parent).toWorld(result, outv);
	  }
  }
  
  /**
   * Transforms the given point by this transformation
   */
  Matrix3f tempMatrix = new Matrix3f();
  public void transform(Vector3f v, Vector3f outv) {
	  outv.set(v);
	  // Scale
	  outv.set(outv.x * S.x, outv.y * S.y, outv.z * S.z);
	  
	  // Rotate
	  tempMatrix.rotX((float)Math.toRadians(R.x));
	  tempMatrix.transform(outv);
	  tempMatrix.rotY((float)Math.toRadians(R.y));
	  tempMatrix.transform(outv);
	  tempMatrix.rotZ((float)Math.toRadians(R.z));
	  tempMatrix.transform(outv);
	  
	  // Translate
	  outv.add(T);
  }
  
  /**
   * Sets the translation vector
   * @param translation
   */
  public void setTranslate(Vector3f translation) {

    T.set(translation);
  }

  /**
   * Sets the scale vector
   * @param scaling
   */
  public void setScale(Vector3f scaling) {

    S.set(scaling);
  }

  /**
   * Sets the rotation angle in degrees
   * @param rotation
   */
  public void setRotate(Vector3f rotation) {

    R.set(rotation);
  }

  /**
   * Gets the translation vector
   * @return
   */
  public Vector3f getTranslate() {

    return new Vector3f(T);
  }

  /**
   * Gets the scale vector
   * @return
   */
  public Vector3f getScale() {

    return new Vector3f(S);
  }

  /**
   * Gets the rotation vector in degrees
   * @return
   */
  public Vector3f getRotate() {

    return new Vector3f(R);
  }

  
  /**
   * @see modeler.SceneNode#render(javax.media.opengl.GL)
   */
  public int render(GL gl, GLU glu) {
	  int outCount = 0;
	  
	  // TODO - fill in code before and after super.render to render children correctly
	  outCount = super.render(gl, glu);
	  
	  return outCount;
  }
  
  /**
   * @see modeler.SceneNode#readData(java.io.ObjectInputStream)
   */
  protected void readData(ObjectInputStream in) throws IOException {

    R.x = in.readFloat();
    R.y = in.readFloat();
    R.z = in.readFloat();
    S.x = in.readFloat();
    S.y = in.readFloat();
    S.z = in.readFloat();
    T.x = in.readFloat();
    T.y = in.readFloat();
    T.z = in.readFloat();
    
  }

  /**
   * @see modeler.SceneNode#saveData(java.io.ObjectOutputStream)
   */
  protected void saveData(ObjectOutputStream out) throws IOException {

    out.writeFloat(R.x);
    out.writeFloat(R.y);
    out.writeFloat(R.z);
    out.writeFloat(S.x);
    out.writeFloat(S.y);
    out.writeFloat(S.z);
    out.writeFloat(T.x);
    out.writeFloat(T.y);
    out.writeFloat(T.z);
    
  }

  /**
   * Concatenates this transform with the currentMatrix
   * @param currMat
   */
  private void addTransform(Matrix4f currMat) {
    
    Matrix4f workMat = new Matrix4f();
    
    workMat.setIdentity();
    workMat.setTranslation(T);
    currMat.mul(workMat);
    
    workMat.setIdentity();
    workMat.set(new AxisAngle4f(zAxis, (float) Math.toRadians(R.z)));
    currMat.mul(workMat);
    
    workMat.setIdentity();
    workMat.set(new AxisAngle4f(yAxis, (float) Math.toRadians(R.y)));
    currMat.mul(workMat);
    
    workMat.setIdentity();
    workMat.set(new AxisAngle4f(xAxis, (float) Math.toRadians(R.x)));
    currMat.mul(workMat);

    workMat.setIdentity();
    workMat.setElement(0, 0, S.x);
    workMat.setElement(1, 1, S.y);
    workMat.setElement(2, 2, S.z);
    currMat.mul(workMat);
  }
  
  /**
   * @see modeler.SceneNode#exportMeshData(java.util.HashMap, java.io.PrintWriter, javax.vecmath.Matrix4f)
   */
  protected void exportMeshData(HashMap materialsMap, PrintWriter pw, Matrix4f mat) {
    addTransform(mat);
    super.exportMeshData(materialsMap, pw, mat);
    
  }

  /**
   * @see javax.swing.tree.TreeNode#getAllowsChildren()
   */
  public boolean getAllowsChildren() {

    return true;

  }

  /**
   * @see javax.swing.tree.TreeNode#isLeaf()
   */
  public boolean isLeaf() {

    return false;

  }

public Vector3f getR() {
	return R;
}

public Vector3f getS() {
	return S;
}

public Vector3f getT() {
	return T;
}
  
}
